﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Web;
using System.Text;
using System.IO;
using GmaWebService.Interfaces;
using System.ServiceModel.Description;
using System.ServiceModel.Channels;
using GmaWebService.Code;
using GmaWebService.Code.Helper;
using System.Xml;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Media.Imaging;
using NLog;
using System.Reflection;

namespace GmaWebService
{
    public class MediaAccessService : IMediaAccessService, IStreamingService
    {

        #region api constants
        private const int VIDEO_API = 1;
        private const int MUSIC_API = 1;
        private const int PICTURES_API = 1;
        private const int TVSERIES_API = 1;
        private const int MOVING_PICTURES_API = 1;
        private const int STREAMING_API = 1;
        #endregion


        #region MediaPortal Attributes

        private static bool m_isDBConnected = false;
        public string m_gentleConfig;
        public string m_connStr;
        public Dictionary<String, WebBannerPath> m_thumbPaths;
        private MovingPictures m_movingPictures;
        private MPTvSeries m_mptvseries;
        private MPMusic m_music;
        private MPVideo m_video;
        private Streaming m_stream;

        #endregion

        static MediaAccessService()
        {
            MPWebStream.MediaTranscoding.Log.RegisterWriter(Log.MPWebStreamCallback);
        }

        public MediaAccessService()
        {
            Log.Info("GmaWebService version 0.3.2");
            m_thumbPaths = Utils.GetWebBannerPaths();
            m_movingPictures = new MovingPictures();
            m_mptvseries = new MPTvSeries();
            m_music = new MPMusic();
            m_video = new MPVideo();
            m_stream = new Streaming();
           
            String user = null;
            String pass = null;
            Utils.GetLogin(out user, out pass, false);

            WcfUsernameValidator.UserName = user;
            WcfUsernameValidator.Password = pass;
        }

        #region MediaPortal
        public WebServiceDescription GetServiceDescription()
        {
            DBLocations db = Utils.GetMPDbLocations();
            WebServiceDescription f = new WebServiceDescription();
            f.SupportsVideos = File.Exists(db.Videos);
            f.VideoApiVersion = VIDEO_API;
            f.SupportsMusic = File.Exists(db.Music);
            f.MusicApiVersion = MUSIC_API;
            f.SupportsPictures = File.Exists(db.Pictures);
            f.PicturesApiVersion = PICTURES_API;
            f.SupportsTvSeries = File.Exists(db.TvSeries);
            f.TvSeriesApiVersion = TVSERIES_API;
            f.SupportsMovingPictures = File.Exists(db.MovingPictures);
            f.MovingPicturesApiVersion = MOVING_PICTURES_API;

            f.ServiceVersion = Assembly.GetExecutingAssembly().GetName().Version.ToString();
            return f;
        }

        #region Music
        public List<WebShare> GetAllMusicShares()
        {
            return m_music.GetAllMusicShares();
        }

        public WebMusicTrack GetMusicTrack(int trackId)
        {
            return m_music.GetMusicTrack(trackId);
        }

        public int GetMusicTracksCount()
        {
            return m_music.GetMusicTracksCount();
        }
        private List<WebMusicTrack> SortWebMusicTrackList(List<WebMusicTrack> tracks, SortBy sort, OrderBy order)
        {
            switch (sort)
            {
                case SortBy.Name:
                    if (order == OrderBy.Desc)
                    {
                        return tracks.OrderByDescending(track => track.Title).ToList();
                    }
                    return tracks.OrderBy(track => track.Title).ToList();
                case SortBy.TrackNumber:
                    if (order == OrderBy.Desc)
                    {
                        return tracks.OrderByDescending(track => track.TrackNumber).ToList();
                    }
                    return tracks.OrderBy(track => track.TrackNumber).ToList();
            }
            return tracks;
        }
        private List<WebMusicArtist> SortWebArtistList(List<WebMusicArtist> artists, OrderBy order)
        {
            switch (order)
            {
                case OrderBy.Desc:
                    return artists.OrderByDescending(artist => artist.Title).ToList();
                case OrderBy.Asc:
                    return artists.OrderBy(artist => artist.Title).ToList();

            }
            return artists;
        }
        private List<WebMusicAlbum> SortWebAlbumList(List<WebMusicAlbum> albums, SortBy sort, OrderBy order)
        {
            switch (sort)
            {
                case SortBy.Name:
                    if (order == OrderBy.Desc)
                    {
                        return albums.OrderByDescending(album => album.Title).ToList();
                    }
                    return albums.OrderBy(album => album.Title).ToList();
                case SortBy.Genre:
                    if (order == OrderBy.Desc)
                    {
                        return albums.OrderByDescending(album => album.Genre).ToList();
                    }
                    return albums.OrderBy(track => track.Genre).ToList();
                case SortBy.Composer:
                    if (order == OrderBy.Desc)
                    {
                        return albums.OrderByDescending(album => album.Composer).ToList();
                    }
                    return albums.OrderBy(track => track.Composer).ToList();
                case SortBy.Year:
                    if (order == OrderBy.Desc)
                    {
                        return albums.OrderByDescending(album => album.Year).ToList();
                    }
                    return albums.OrderBy(track => track.Year).ToList();

            }
            return albums;
        }

        public List<WebMusicTrack> GetAllMusicTracks(SortBy sort = SortBy.Name, OrderBy order = OrderBy.Asc)
        {
            return SortWebMusicTrackList(m_music.GetAllMusicTracks(), sort, order);
        }

        public List<WebMusicTrack> GetMusicTracks(int startIndex, int endIndex, SortBy sort = SortBy.Name, OrderBy order = OrderBy.Asc)
        {
            return SortWebMusicTrackList(m_music.GetAllMusicTracks(), sort, order).GetRange(startIndex, endIndex - startIndex);
        }

        public List<WebMusicAlbum> GetAllAlbums(SortBy sort = SortBy.Name, OrderBy order = OrderBy.Asc)
        {
            return SortWebAlbumList(m_music.GetAllAlbums(), sort, order);
        }


        public List<WebMusicAlbum> GetAlbums(int startIndex, int endIndex, SortBy sort = SortBy.Name, OrderBy order = OrderBy.Asc)
        {
            List<WebMusicAlbum> albums = SortWebAlbumList(m_music.GetAllAlbums(), sort, order).GetRange(startIndex, endIndex - startIndex + 1);
            return albums;
        }


        public int GetAlbumsCount()
        {
            return m_music.GetAlbumsCount();
        }


        public List<WebMusicArtist> GetAllArtists(OrderBy order = OrderBy.Asc)
        {
            return SortWebArtistList(m_music.GetAllArtists(), order);
        }

        public List<WebMusicArtist> GetArtists(int startIndex, int endIndex, OrderBy order = OrderBy.Asc)
        {
            List<WebMusicArtist> artists = SortWebArtistList(m_music.GetAllArtists(), order).GetRange(startIndex, endIndex - startIndex + 1);
            return artists;
        }

        public WebMusicAlbum GetAlbum(string albumArtistName, string albumName)
        {
            return m_music.GetAlbum(albumName, albumArtistName);
        }

        public int GetArtistsCount()
        {
            return m_music.GetArtistsCount();
        }


        public List<WebMusicAlbum> GetAlbumsByArtist(String artistName, SortBy sort = SortBy.Year, OrderBy order = OrderBy.Asc)
        {
            return SortWebAlbumList(m_music.GetAlbumsByArtist(artistName), sort, order);
        }


        public List<WebMusicTrack> GetSongsOfAlbum(String albumName, String albumArtistName, SortBy sort = SortBy.TrackNumber, OrderBy order = OrderBy.Asc)
        {
            return SortWebMusicTrackList(m_music.GetSongsOfAlbum(albumName, albumArtistName), sort, order);
        }


        public List<WebMusicTrack> FindMusicTracks(string album, string artist, string title, SortBy sort = SortBy.Name, OrderBy order = OrderBy.Asc)
        {
            return SortWebMusicTrackList(m_music.FindMusicTracks(album, artist, title), sort, order);
        }
        #endregion
        #region videos
        public List<WebShare> GetVideoShares()
        {
            return m_video.GetAllShares();
        }

        public int GetVideosCount()
        {
            return m_video.GetVideosCount();
        }

        private List<WebMovie> SortWebMovieList(List<WebMovie> movies, SortBy sort, OrderBy order)
        {
            switch (sort)
            {
                case SortBy.Name:
                    if (order == OrderBy.Desc)
                    {
                        return movies.OrderByDescending(movie => movie.Title).ToList();
                    }
                    return movies.OrderBy(movie => movie.Title).ToList();
                case SortBy.Genre:
                    if (order == OrderBy.Desc)
                    {
                        return movies.OrderByDescending(movie => movie.Genre).ToList();
                    }
                    return movies.OrderBy(movie => movie.Genre).ToList();
                case SortBy.Year:
                    if (order == OrderBy.Desc)
                    {
                        return movies.OrderByDescending(movie => movie.Year).ToList();
                    }
                    return movies.OrderBy(movie => movie.Year).ToList();
            }

            return movies;
        }

        public List<WebMovie> GetAllVideos(SortBy sort = SortBy.Name, OrderBy order = OrderBy.Asc)
        {
            List<WebMovie> videos = SortWebMovieList(m_video.GetAllVideos(), sort, order); //get all movies from beginning to end
            return videos;
        }


        public List<WebMovie> GetVideos(int startIndex, int endIndex, SortBy sort = SortBy.Name, OrderBy order = OrderBy.Asc)
        {
            List<WebMovie> videos = SortWebMovieList(m_video.GetAllVideos(), sort, order).GetRange(startIndex, endIndex - startIndex + 1);
            return videos;
        }

        public WebMovieFull GetFullVideo(int videoId)
        {
            return m_video.GetFullVideo(videoId);
        }

        #endregion
        #region TvSeries

        public int GetSeriesCount()
        {
            return m_mptvseries.GetSeriesCount();
        }

        private List<WebSeries> SortWebSeriesList(List<WebSeries> series, SortBy sort, OrderBy order)
        {
            switch (sort)
            {
                case SortBy.Name:
                    if (order == OrderBy.Desc)
                    {
                        return series.OrderByDescending(serie => serie.PrettyName).ToList();
                    }
                    return series.OrderBy(serie => serie.PrettyName).ToList();
                case SortBy.Rating:
                    if (order == OrderBy.Desc)
                    {
                        return series.OrderByDescending(serie => serie.Rating).ToList();
                    }
                    return series.OrderBy(serie => serie.Rating).ToList();
                case SortBy.Genre:
                    if (order == OrderBy.Desc)
                    {
                        return series.OrderByDescending(serie => serie.GenreString).ToList();
                    }
                    return series.OrderBy(serie => serie.GenreString).ToList();
            }


            return series;
        }
        private List<WebSeason> SortWebSeasonList(List<WebSeason> seasons, OrderBy order)
        {
            switch (order)
            {
                case OrderBy.Desc:
                    return seasons.OrderByDescending(season => season.SeriesId).ThenByDescending(season => season.SeasonNumber).ToList();
                case OrderBy.Asc:
                    return seasons.OrderBy(season => season.SeriesId).ThenBy(season => season.SeasonNumber).ToList();

            }
            return seasons;
        }
        private List<WebEpisode> SortWebEpisodeList(List<WebEpisode> episodes, SortBy sort, OrderBy order)
        {
            switch (sort)
            {
                case SortBy.SeasonNumber_EpisodeNumber:
                    if (order == OrderBy.Desc)
                    {
                        return episodes.OrderByDescending(episode => episode.IdSerie).ThenByDescending(episode => episode.SeasonNumber).ThenByDescending(episode => episode.EpisodeNumber).ToList();
                    }
                    return episodes.OrderBy(episode => episode.IdSerie).ThenBy(episode => episode.SeasonNumber).ThenBy(episode => episode.EpisodeNumber).ToList();
                case SortBy.Date:
                    if (order == OrderBy.Desc)
                    {
                        return episodes.OrderByDescending(episode => episode.FirstAired).ToList();
                    }
                    return episodes.OrderBy(episode => episode.FirstAired).ToList();
                case SortBy.Rating:
                    if (order == OrderBy.Desc)
                    {
                        return episodes.OrderByDescending(episode => episode.Rating).ToList();
                    }
                    return episodes.OrderBy(episode => episode.Rating).ToList();
                case SortBy.Name:
                    if (order == OrderBy.Desc)
                    {
                        return episodes.OrderByDescending(episode => episode.Name).ToList();
                    }
                    return episodes.OrderBy(episode => episode.Name).ToList();
            }
            return episodes;
        }

        public List<WebSeries> GetAllSeries(SortBy sort = SortBy.Name, OrderBy order = OrderBy.Asc)
        {
            return SortWebSeriesList(m_mptvseries.GetAllSeries(), sort, order);
        }


        public List<WebSeries> GetSeries(int startIndex, int endIndex, SortBy sort = SortBy.Name, OrderBy order = OrderBy.Asc)
        {
            List<WebSeries> retList = SortWebSeriesList(m_mptvseries.GetAllSeries(), sort, order).GetRange(startIndex, endIndex - startIndex + 1);
            return retList;
        }


        public WebSeriesFull GetFullSeries(int seriesId)
        {
            return m_mptvseries.GetFullSeries(seriesId);
        }


        public List<WebSeason> GetAllSeasons(int seriesId, OrderBy order = OrderBy.Asc)
        {
            return SortWebSeasonList(m_mptvseries.GetAllSeasons(seriesId), order);//all seasons;
        }


        public WebSeason GetSeason(int seriesId, int seasonNumber)
        {
            return m_mptvseries.GetSeason(seriesId, seasonNumber);
        }


        public List<WebEpisode> GetAllEpisodes(int seriesId, SortBy sort = SortBy.SeasonNumber_EpisodeNumber, OrderBy order = OrderBy.Asc)
        {
            return SortWebEpisodeList(m_mptvseries.GetAllEpisodes(seriesId), sort, order);
        }


        public List<WebEpisode> GetAllEpisodesForSeason(int seriesId, int seasonNumber, SortBy sort = SortBy.EpisodeNumber, OrderBy order = OrderBy.Asc)
        {
            return SortWebEpisodeList(m_mptvseries.GetAllEpisodesForSeason(seriesId, seasonNumber), sort, order);
        }


        public List<WebEpisode> GetEpisodes(int seriesId, int startIndex, int endIndex, SortBy sort = SortBy.SeasonNumber_EpisodeNumber, OrderBy order = OrderBy.Asc)
        {
            return SortWebEpisodeList(m_mptvseries.GetAllEpisodes(seriesId), sort, order).GetRange(startIndex, endIndex - startIndex);
        }


        public List<WebEpisode> GetEpisodesForSeason(int seriesId, int seasonNumber, int startIndex, int endIndex, SortBy sort = SortBy.EpisodeNumber, OrderBy order = OrderBy.Asc)
        {
            return SortWebEpisodeList(m_mptvseries.GetAllEpisodesForSeason(seriesId, seasonNumber), sort, order).GetRange(startIndex, endIndex - startIndex);
        }


        public int GetEpisodesCount(int seriesId)
        {
            return m_mptvseries.GetEpisodesCount(seriesId);
        }


        public int GetEpisodesCountForSeason(int seriesId, int season)
        {
            return m_mptvseries.GetEpisodesCountForSeason(seriesId, season);
        }


        public WebEpisodeFull GetFullEpisode(int episodeId)
        {
            return m_mptvseries.GetFullEpisode(episodeId);
        }

        #endregion
        #region Movies

        public WebMovieFull GetFullMovie(int movieId)
        {
            return m_movingPictures.GetFullMovie(movieId);
        }


        public int GetMovieCount()
        {
            return m_movingPictures.GetMovieCount();
        }


        public List<WebMovie> GetAllMovies(SortBy sort = SortBy.Name, OrderBy order = OrderBy.Asc)
        {
            return SortWebMovieList(m_movingPictures.GetAllMovies(), sort, order);//get all movies from beginning to end
        }


        public List<WebMovie> GetMovies(int startIndex, int endIndex, SortBy sort = SortBy.Name, OrderBy order = OrderBy.Asc)
        {
            return SortWebMovieList(m_movingPictures.GetAllMovies(), sort, order).GetRange(startIndex, endIndex - startIndex);
        }


        public List<WebMovie> SearchForMovie(String searchString)
        {
            return m_movingPictures.SearchForMovie(searchString);
        }

        #endregion
        #region Pictures

        public List<WebShare> GetPictureRootShares()
        {
            return MPPictures.GetAllShares();
        }

        public WebPictureDirectory GetPictureDirectory(string path)
        {
            return MPPictures.GetPictureDirectory(path);
        }

        #endregion

        #endregion

        #region FileSystemAccess
        public List<String> GetDirectoryListByPath(string path)
        {
            return FileSystem.GetDirectoryListByPath(path);
        }

        public List<WebFileInfo> GetFilesFromDirectory(string path)
        {
            return FileSystem.GetFilesFromDirectory(path);
        }

        public WebFileInfo GetFileInfo(MediaItemType type, string itemId)
        {
            string path = ResolvePath(type, itemId);
            Log.Debug("GetFileInfo: type=" + type + ", id=" + itemId + ", path=" + path);
            return FileSystem.GetFileInfo(path);
        }


        public byte[] GetFile(string path)
        {
            return FileSystem.GetFile(path);
        }
        #endregion

        #region Media
        /// <summary>
        /// Get the full path to a file as specified by the client
        /// </summary>
        /// <param name="type">The type of media to retrieve</param>
        /// <param name="itemId">The ID of the media to retrieve (id for series, movie, db video, musictrack and path for shares item)</param>
        /// <returns>The full path to the file</returns>
        private string ResolvePath(MediaItemType type, string itemId)
        {
            switch (type)
            {
                case MediaItemType.MusicShareItem:
                    if (m_music.CheckPathValid(itemId))
                        return itemId;
                    break;
                case MediaItemType.VideoShareItem:
                    if (m_video.CheckPathValid(itemId))
                        return itemId;
                    break;
                case MediaItemType.VideoDatabaseItem:
                    return m_video.GetVideoPath(itemId);
                case MediaItemType.TvSeriesItem:
                    return m_mptvseries.GetSeriesPath(itemId);
                case MediaItemType.MovieItem:
                    return m_movingPictures.GetMoviePath(itemId);
                case MediaItemType.MusicTrackItem:
                    return m_music.GetTrackPath(itemId);
            }

            return null;
        }

        /// <summary>
        /// Returns the image object of the given path or null if the image doesn't exists
        /// </summary>
        /// <param name="path">Path to image</param>
        /// <returns>Stream of image or null</returns>
        public Stream GetImage(string path)
        {
            return FileSystem.GetImage(path);
        }

        /// <summary>
        /// Returns the image object of the given path, resized to fit the maxWidth and maxHeight
        /// parameters, or null if the image doesn't exists
        /// </summary>
        /// <param name="path">Path to image</param>
        /// <param name="maxWidth">Maximum width of image</param>
        /// <param name="maxHeight">Maximum height of image</param>
        /// <returns>Stream of image or null</returns>
        public Stream GetImageResized(string path, int maxWidth, int maxHeight)
        {
            return FileSystem.GetImageResized(path, maxWidth, maxHeight);
        }

        /// <summary>
        /// Returns the stream to a media item (video, music, image,...)
        /// </summary>
        /// <param name="type">Type of the item</param>
        /// <param name="itemId">Id of the item (id for series, movie, db video, musictrac and path for shares item)</param>
        /// <returns>Stream of file or null if file not found</returns>
        public Stream GetMediaItem(MediaItemType type, string itemId)
        {
            string path = ResolvePath(type, itemId);
            Log.Info("GetMediaItem: type=" + type + ", id=" + itemId + ", path=" + path);
            return FileSystem.GetMediaItem(path);
        }
        #endregion

        #region Streaming
        public List<WebTranscoderProfile> GetTranscoderProfiles()
        {
            return m_stream.GetTranscoderProfiles().Select(p => p.ToWebTranscoderProfile()).ToList();
        }

        public List<WebTranscoderProfile> GetTranscoderProfilesForTarget(String target)
        {
            return m_stream.GetTranscoderProfiles().Select(p => p.ToWebTranscoderProfile()).Where(p => p.Target == target).ToList();
        }

        /* OK, this got a bit complicated with all the problems related to Android streaming and seeking. To
         * do a simple transcoded stream retrieval, use the following methods:
         * 
         * - RequestStream(type, itemId, profileName, identifier): request a stream
         *   - type and itemId specify the source
         *   - profileName is the name of the transcoder
         *   - clientDescription is a human-readable description of the client
         *   - identifier is a unique identifier for this stream, which needs to be passed to all following methods and is required to be unique.
         * - StartStream(identifier, startPosition): actually start the stream
         *   - startPosition is the start time of the playback in seconds 
         * - RetrieveStream(identifier): get the stream. This URL can also be passed to an external player and starts almost immediately.
         * - StartStream(identifier, startPosition): seek to another position in the file
         *   - When this method is called the stream send by RetrieveStream reaches end-of-stream. It is safe to close that connection.
         * - FinishStream(identifier): finish the transcoding and do final cleanup. Has same effect upon RetrieveStream as StartStream() described above.
         */
        public bool InitStream(MediaItemType type, string itemId, string clientDescription, string identifier)
        {
            Log.Info("InitStream called: type=" + type + ", item=" + itemId +  ", client=" + clientDescription + ", ident=" + identifier);
            return m_stream.InitStream(identifier, clientDescription, ResolvePath(type, itemId));
        }

        public bool StartStream(string identifier, string profileName, int startPosition)
        {
            Log.Info("StartStream called: pos=" + startPosition + ", profile=" + profileName + ", ident=" + identifier);
            m_stream.EndStream(identifier); // first stop the previous stream, if any
            return m_stream.StartStream(identifier, m_stream.GetTranscoderProfileByName(profileName), startPosition); // then start it
        }

        public Stream RetrieveStream(string identifier)
        {
            Log.Info("RetrieveStream called: ident=" + identifier);
            return m_stream.RetrieveStream(identifier);
        }

        public bool FinishStream(string identifier)
        {
            Log.Info("FinishStream called: ident=" + identifier);
            m_stream.EndStream(identifier);
            m_stream.KillStream(identifier);
            return true;
        }

        public List<WebStreamingSession> GetStreamingSessions()
        {
            return m_stream.GetStreamingSessions();
        }

        public WebTranscodingInfo GetTranscodingInfo(string identifier)
        {
            return m_stream.GetTranscodingInfo(identifier);
        }

        public WebMediaInfo GetMediaInfo(MediaItemType type, string itemId)
        {
            Log.Info("GetMedaInfo called: type=" + type + ", itemId=" + itemId);
            string path = ResolvePath(type, itemId);
            if (path != null)
            {
                return m_stream.GetMediaInfo(path);
            }
            else
            {
                Log.Warn("Item " + type + "|" + itemId + " doesn't exist on this system");
                return null;
            }
        }

        public Stream ExtractImage(MediaItemType type, string itemId, int position)
        { 
            string path = ResolvePath(type, itemId);
            if (path != null)
            {
                return m_stream.ExtractImage(path, position, null, null);
            }
            else
            {
                Log.Warn("Item " + type + "|" + itemId + " doesn't exist on this system");
                return null;
            }
        }

        public Stream ExtractImageResized(MediaItemType type, string itemId, int position, int maxWidth, int maxHeight)
        {
            string path = ResolvePath(type, itemId);
            if (path != null)
            {
                return m_stream.ExtractImage(path, position, maxWidth, maxHeight);
            }
            else
            {
                Log.Warn("Item " + type + "|" + itemId + " doesn't exist on this system");
                return null;
            }
        }
        #endregion
    }
}